趁最後幾天衝一下我很愛的分散式資料庫Cassandra。如果要用30秒的電梯演講
說明Cassandra的優點,我會說:
Cassandra是個開放源碼、分散式、去中心式、高擴展性、高可用、容錯、可調式一致性、列導向資料庫。其分散式設計與資料模型分別奠基於Amazon Dynamo與Google Bigtable,現有許多大型網站採用。
而分散式
、去中心式
、高擴展性
、高可用
、容錯
、可調式一致性
這每一項在Cassandra背後都有其對應的實現理論與技術。若你曾經聽過有人或文章在Cassandra的架構,那你可能有聽到例如Gossip
、Token Ring
、Virtua Node
、Partitioner
、Replication Strategy
、Consistency Level
、MemTable
、SSTable
、Query-Driven
、Data Modeling
等一堆相關技術名詞。但這次主題不是Cassandra 30天鐵人賽XD,有興趣進一步研究的,我推薦這本:
O'REILLY出版社大家都不陌生吧。這本基於Cassandra最新主板號(3.0)2016年6月才出版的Definitive Guide,內容相當完整。
中文譯版準時的話估計今年年中之前吧(....就看我有沒有拖稿了XD)。
廢話不多說,先來玩看看吧:
下載目前最新的3.9版本:apache-cassandra-3.9-bin.tar.gz
。Cassandra的版本號是用Intel那套Tick-Tock
準則,而且每個月幾乎都會更新版本號(即便打開release note沒做啥事)。所以內容與3.0差異並沒有想像中那麼大。下載完後,可以在資料夾內透過:
$bin/cassandra
直接啟動服務,此時螢幕上會開始刷訊息...如果一切順利,應該會看到如下畫面:
並且再最後一行看到:JUMP TO NORMAL。
此prompt接著會被釋放(如果沒有就自己按一下ENTER試試),不像zookeeper或kafka會咬死XD,而初步探索Cassandra最好用的就是透過python實作的cqlsh(Cassandra Query Language shell)來玩啦,透過:bin/cqlsh
直接連接吧:
joechh@joechh:/opt/apache-cassandra-3.9$ bin/cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4]
Use HELP for help.
cqlsh>
注意如果你有修改cassandra.yaml檔更改IP或是Client端的port,那在cqlsh後面就要指定對應的IP跟port,都沒指定預設會走127.0.0.1:9042。
進到shell可以先用help
看一下可以用的指令總表
若對某個指令的細部參數想進一步了解可以用help COMMAND
,會跳出對應的網頁參考資料。
描述一下叢集(也可用於keyspace與table)
cqlsh:testspace> DESCRIBE CLUSTER
Cluster: Test Cluster
Partitioner: Murmur3Partitioner
Range ownership:
'testspace'
我們可以看到這座測試叢集的一些相關資訊,裏面還有一個很重要的Partitioner資訊(預設採用Murmur3Partitioner
),繼續看下去吧。
keyspace勒:
cqlsh:testspace> DESCRIBE KEYSPACES;
system_schema system_auth system system_distributed system_traces
可以將keyspace假想成RDB中的資料庫等級的階層,內含多個tables。
想知道版本號也沒問題:
cqlsh:testspace> show version
[cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4]
其實這個就是shell登入畫面的一部份啦~注意的是Cassandra中有許多小元件,每個重要元件都有其版本,像是cqlsh、native protocol等,而最重要的就是Cassandra版本啦(3.9版)
[Snippet.62] Create Keyspace/Table in Cassandra
玩資料庫當然要會建keyspace(DB)跟表阿,看看怎麼做,
cqlsh:testspace> create KEYSPACE my_keyspace with replication = {'class': 'SimpleStrategy', 'replication_factor': 1 };
建立過程中請愛用TAB,cassandra有支援syntax complete的補齊功能,就像Linux下指令一樣。你會愛上他的。用describe KEYSPACE my_keyspace
看一下剛建好的keyspace吧:
cqlsh:>use my_keyspace;
cqlsh:my_keyspace> CREATE TABLE user (first_name text, last_name text, primary key(first_name));
[Snippet.63] CURD in Cassandra
先來放筆資料進去,怎麼放?Insert into
囉
cqlsh:my_keyspace> INSERT INTO user (first_name, last_name) VALUES ( 'Joe','Hsu');
有筆資料了,先來個RDB最愛的起手式select * from
大法XD:
cqlsh:my_keyspace> SELECT * from user ;
first_name | last_name
------------+-----------
Joe | Hsu
常見的Aggregation函式當然也沒問題啦:
cqlsh:my_keyspace> SELECT COUNT(*) FROM user ;
count
-------
1
Where過慮器一定要有的:
cqlsh:my_keyspace> SELECT * from user WHERE first_name='Joe';
first_name | last_name
------------+-----------
Joe | Hsu
刪除某個欄位的值:
cqlsh:my_keyspace> DELETE last_name from user WHERE first_name='Joe';
cqlsh:my_keyspace> SELECT * from user WHERE first_name='Joe';
first_name | last_name
------------+-----------
Joe | null
清掉整張表的內容:
cqlsh:my_keyspace> TRUNCATE user;
cqlsh:my_keyspace> SELECT * from user WHERE first_name='Joe';
first_name | last_name
------------+-----------
刪除表:
cqlsh:my_keyspace> drop table user;
cqlsh:my_keyspace> desc TABLEs;
<empty>
接著我們將表跟值建回來以利後續使用,知道怎麼建了吧
接下來加個欄位:
cqlsh:my_keyspace> ALTER TABLE user ADD title text;
cqlsh:my_keyspace> SELECT * from user;
first_name | last_name | title
------------+-----------+-------
Joe | Hsu | null
多放幾筆資料進來:
cqlsh:my_keyspace> INSERT INTO user(first_name , last_name , title ) VALUES ( 'Chang', 'Wen-Ting', 'Mrs.');
cqlsh:my_keyspace> INSERT INTO user(first_name , last_name) VALUES ( 'Mary','Walejake');
cqlsh:my_keyspace> SELECT * from user;
first_name | last_name | title
------------+-----------+-------
Joe | Hsu | null
Mary | Walejake | null
Chang | Wen-Ting | Mrs.
此外,Cassandra這種每個值都有個Key與之對應的資料庫,每個欄位都有各別的寫入時間(這個時間其實會被拿來做很多事情,例如是節點間資料副本版本比對看哪份較新之類的):
cqlsh:my_keyspace> SELECT first_name, last_name, writetime(last_name) from user;
first_name | last_name | writetime(last_name)
------------+-----------+----------------------
Joe | Hsu | 1484343504750490
Mary | Walejake | 1484343738096185
Chang | Wen-Ting | 1484343690394195
但Cassandra目前尚不允許對主鍵查TS....
cqlsh:my_keyspace> SELECT first_name, last_name, writetime(first_name) from user;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot use selection function writeTime on PRIMARY KEY part first_name"
我們可以對欄位
設定TTL(Time-to-Live)
,讓他自動過時消失,而這在HouseKeeping
時相當有用,不用自己手動維護程式或寫script來砍資料,該怎麼設定呢?
cqlsh:my_keyspace> SELECT first_name, last_name, TTL(last_name) from user where first_name = 'Joe' ;
first_name | last_name | ttl(last_name)
------------+-----------+----------------
Joe | Hsu | null
預設null代表沒有設定,用UPDATE
設上去吧(單位是秒)
cqlsh:my_keyspace> UPDATE user USING TTL 3600 SET last_name= 'Hue' WHERE first_name = 'Joe' ;
cqlsh:my_keyspace> SELECT first_name, last_name, TTL(last_name) from user where first_name = 'Joe' ;
first_name | last_name | ttl(last_name)
------------+-----------+----------------
Joe | Hue | 3597
設上去的一瞬間就開始倒數哩。
Cassandra主要有下列幾種不同的資料型別:
Cassandra裏面沒有固定大小的字串(RDB的char),絕大多數情境可以直接使用text(就例如Java的String一般)
我們會選一些比較特別的來玩看看,包含uuid
、set
、list
、map
、UDT
等
先玩玩uuid,uuid全名是universally unique identifier,因為cassandra是一個分散式資料庫,他會用一些取名技巧保證此identifier即便是整座叢集中都是唯一的,那我們該如何設定uuid呢?Cassandra已經幫我們準備好函式了:
cqlsh:my_keyspace> ALTERT TABLE user ADD id uuid;
cqlsh:my_keyspace> UPDATE user SET id = uuid() WHERE first_name='Mary';
cqlsh:my_keyspace> SELECT first_name,id FROM user WHERE first_name='Mary';
first_name | id
------------+--------------------------------------
Mary | 6b8e8506-9416-48b9-baf7-1717bc5d37c8
透過uuid()函式設定值
再來看看set與list這類好用的集合型別:
先新增個email的set欄位並取值
cqlsh:my_keyspace> ALTER TABLE user ADD emails set<text>;
cqlsh:my_keyspace> UPDATE user SET emails ={'joe@example.com','joe2@mail.com'} WHERE first_name ='Joe';
cqlsh:my_keyspace> SELECT emails FROM user WHERE first_name='Joe' ;
emails
--------------------------------------
{'joe2@mail.com', 'joe@example.com'}
那LISTS勒:
cqlsh:my_keyspace> ALTER TABLE user ADD phone_numbers list<text>
cqlsh:my_keyspace> UPDATE user SET phone_numbers=['02-2782-1234'] where first_name ='Joe' ;
cqlsh:my_keyspace> SELECT phone_numbers FROM user WHERE first_name = 'Joe';
phone_numbers
------------------
['02-2782-1234']
如果我想將一筆新的值加入集合該怎麼做?以List為例好了
cqlsh:my_keyspace> UPDATE user SET phone_numbers = phone_numbers + ['07-371-8888'] WHERE first_name ='Joe';
cqlsh:my_keyspace> SELECT phone_numbers FROM user WHERE first_name = 'Joe';
phone_numbers
---------------------------------
['02-2782-1234', '07-371-8888']
可以用類似phone_numbers + ['07-371-8888']
這種語法加進去,如果我想調整把新的值放到List的最前面可以嗎?當然可以阿,那就['07-371-8888'] + phone_numbers
即可
[Snippet.64] Cassandra User Defined Type
建立自定義類別可以讓資料的表示方式更有彈性,就像物件導向一樣,把一個概念封裝成一個型別,那該怎麼建立勒?就像建個表一樣!
cqlsh:my_keyspace> CREATE TYPE address(
... street text,
... city text,
... state text,
... zip_code int);
有沒有發現,幾乎跟開表一樣,只是Type沒有主鍵
而已。你說沒有主鍵沒啥阿~那是因為Cassandra中所有表都有primary key!!這與傳統DB有點不同。而Cassandra的primary key與RDB的作用有非常大非常大的不同,這部份就不多說了,基本上為Cassandra設計表時有很大的功夫是在設定一個符合查詢要求的primary key,這也是Query-Driven的由來。
有了UDT之後,把他放入之前的user表吧,但是一個人地址可能不一樣,那能不能把他指定為集合物件的型別勒?
cqlsh:my_keyspace> ALTER TABLE user ADD addresses map<text,address>;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Non-frozen UDTs are not allowed inside collections: map<text, address>"
結果很不幸的...如果要直接定義不行XD。原因是集合物件還沒有完全
支援自定義型別。所以社群就想了個方式,用freeze
限制住UDT在集合內的一些能力,好讓他能先放入集合中。再未來的版本中,如果開發完成,則會有unfreeze
這類的功能,來看看怎麼宣告吧:
cqlsh:my_keyspace> ALTER TABLE user ADD addresses map<text,frozen<address>>;
現在對user表執行DESCRIBE就會看到UDT型別:
cqlsh:my_keyspace> DESCRIBE TABLE user ;
CREATE TABLE my_keyspace.user (
first_name text PRIMARY KEY,
addresses map<text, frozen<address>>,
emails set<text>,
id uuid,
last_name text,
phone_numbers list<text>,
title text
)
//..以下資訊略
改完當然要放個值進去然後讀看看囉~
cqlsh:my_keyspace> UPDATE user SET addresses=
... addresses +{'home':{street:'7712 E. haha Road',
...city:'Taaa',state:'AW',zip_code:55678}}
...WHERE first_name='Joe' ;
這樣可以仔細看看map與UDT寫值的方式!成功囉,看看結果(太寬用截圖的XD):
有沒有感覺少了塊啥?在RDB中很重要的??.....沒錯,就是Join!!。Cassandra與許多分散式NoSQL一樣。不提供Join操作。這時候Spark
就可以來補足這塊啦:可以將資料從Cassandra讀出來,載入Spark執行一些Cassandra架構上不適合的操作
!加上Cassandra持續改進Spark-connector
的效能與支援度。最後就變成好朋友,雙雙成為SMACK
成員啦。
基礎的Cassandra安裝與操作就到此,明天就看看怎麼透過Scala+Spark與Cassandra互動吧!